#include "graphics.opengl/renderer.h"
#include "graphics.opengl/vertexbuffer.h"
#include "graphics.opengl/texture.h"
#include "graphics.opengl/shader.h"
#include "graphics.opengl/program.h"
#include "graphics.opengl/query.h"

#define BUFFER_OFFSET(i)	((char*)NULL + (i))

namespace dw
{
namespace graphics
{
namespace opengl
{

GLenum Renderer::_glCullMode[3] =
{ 
	GL_FRONT, 
	GL_BACK, 
	GL_FRONT_AND_BACK 
};

GLenum Renderer::_glFillMode[3] =
{ 
	GL_POINT,
	GL_LINE,
	GL_FILL 
};

GLenum Renderer::_glShadeMode[2] =
{ 
	GL_FLAT, 
	GL_SMOOTH
};

GLenum Renderer::_glCmpFunc[8] =
{
	GL_GREATER, 
	GL_LESS, 
	GL_GEQUAL, 
	GL_LEQUAL, 
	GL_NOTEQUAL, 
	GL_EQUAL, 
	GL_ALWAYS, 
	GL_NEVER
};

GLenum Renderer::_glBlendFunc[15] =
{
	GL_ZERO,
	GL_ONE,
	GL_DST_COLOR,
	GL_SRC_COLOR,
	GL_ONE_MINUS_DST_COLOR,
	GL_ONE_MINUS_SRC_COLOR,
	GL_SRC_ALPHA,
	GL_ONE_MINUS_SRC_ALPHA,
	GL_DST_ALPHA,
	GL_ONE_MINUS_DST_ALPHA,
	GL_SRC_ALPHA_SATURATE,
	GL_CONSTANT_COLOR_EXT,
	GL_ONE_MINUS_CONSTANT_COLOR_EXT,
	GL_CONSTANT_ALPHA_EXT,
	GL_ONE_MINUS_CONSTANT_ALPHA_EXT
};

GLenum Renderer::_glBlendOp[6] =
{	
	GL_FUNC_ADD_EXT,
	GL_FUNC_SUBTRACT_EXT,
	GL_FUNC_REVERSE_SUBTRACT_EXT,
	GL_MIN_EXT,
	GL_MAX_EXT
};

GLenum Renderer::_glPixelFormat[6] =
{
	GL_COLOR_INDEX,
	GL_RGB,
	GL_RGBA,
	GL_BGR_EXT,
	GL_BGRA_EXT,
	GL_LUMINANCE
};

Renderer::Renderer()
:	dw::graphics::Renderer()
{
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		dwFail("Failed to initialize GLEW\n");
		dwLogError("Failed to initialize GLEW : '%s'\n", glewGetErrorString(err));
	}

	ilInit();
	
	_name				=		reinterpret_cast< const char* >(glGetString(GL_RENDERER));
	_version		=		reinterpret_cast< const char* >(glGetString(GL_VERSION));
	_vendor			=		reinterpret_cast< const char* >(glGetString(GL_VENDOR));
	_extensions =		reinterpret_cast< const char* >(glGetString(GL_EXTENSIONS));		
	_extensions +=	reinterpret_cast< const char* >(wglGetExtensionsStringARB(wglGetCurrentDC()));

	// Get the number of available texture unit on the hardware
	glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &_caps.maxTextureUnits);
	_textureUnits = new TextureUnit[_caps.maxTextureUnits];

	// Initialize the texcoord mapping for each texture unit eg. tu0 = mapchannel0, tu1 = mapchannel1...
	for (int i = 0; i < _caps.maxTextureUnits; ++i)
		setTextureUnitState(i, TS_TEXCOORD_INDEX, i);

	glGetIntegerv(GL_MAX_LIGHTS, &_caps.maxActiveLights);

	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	wglSwapIntervalEXT(0);
}

Renderer::~Renderer()
{
	if (_textureUnits)
		delete[] _textureUnits;
}

bool Renderer::supportExtension(const std::string& extension) const
{
	if (_extensions.find(extension, 0) == std::string::npos)
		return false;
	return true;
}

void Renderer::setPerspective(double fov, double aspect, double zNear, double zFar)
{
	//const double pi = 3.1415926535897932384626433832795;
	//double fH, fW;

	//fH = tan(fov / 180 * pi) * zNear / 2;
	//fH = tan(fov / 2.0);
	//fW = fH * aspect;
	//glFrustum(-fW, fW, -fH, fH, zNear, zFar);
	gluPerspective(fov, aspect, zNear, zFar);
}
void Renderer::setOrtho2D(double left, double right, double bottom, double top,double zNear, double zFar )
{
	glOrtho(left, right, bottom, top,zNear,zFar);
}

void Renderer::pushState()
{
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS );
}

void Renderer::popState()
{
	glPopAttrib();
	glPopClientAttrib();
	m_indexBuffer = 0;
}

void Renderer::setState(const State& state)
{
	setRenderState(RS_LIGHTING, state.value(RS_LIGHTING));
	setRenderState(RS_CULLING, state.value(RS_CULLING));
	setRenderState(RS_DEPTH_TEST, state.value(RS_DEPTH_TEST));
	setRenderState(RS_ALPHA_TEST, state.value(RS_ALPHA_TEST));
	setRenderState(RS_CULL_MODE, state.value(RS_CULL_MODE));
	setRenderState(RS_FILL_MODE, state.value(RS_FILL_MODE));
	setRenderState(RS_SHADE_MODE, state.value(RS_SHADE_MODE));
	setRenderState(RS_DEPTH_FUNC, state.value(RS_DEPTH_FUNC));
	setRenderState(RS_ALPHA_FUNC, state.value(RS_ALPHA_FUNC));
	setRenderState(RS_STENCIL_FUNC, state.value(RS_STENCIL_FUNC));
	setRenderState(RS_BLEND_SRC_FUNC, state.value(RS_BLEND_SRC_FUNC));
	setRenderState(RS_BLEND_DST_FUNC, state.value(RS_BLEND_DST_FUNC));
	setRenderState(RS_BLEND_OP, state.value(RS_BLEND_OP));
	//setRenderState(BLEND_COLOR, state.blendColor);
}

void Renderer::setMatrixMode(const MatrixMode& mode)
{
	if (mode == MODELVIEW)
		glMatrixMode(GL_MODELVIEW);
	else if (mode == PROJECTION)
		glMatrixMode(GL_PROJECTION);
}

void Renderer::setColor(const Colorf& color)
{
	setColor(color.r, color.g, color.b, color.a);
}

void Renderer::setColor(const float& r, const float& g, const float& b, const float& a)
{
	glColor4f(r, g, b, a);
}

void Renderer::setViewport(const unsigned x, const unsigned y, const unsigned width, const unsigned height)
{
	glViewport(x, y, width, height);
}

void Renderer::setClipRect(const unsigned x, const unsigned y, const unsigned width, const unsigned height)
{
	glEnable(GL_SCISSOR_TEST);
	glScissor(x, y, width, height);
}

void Renderer::loadIdentity()
{
	glLoadIdentity();
}

void Renderer::pushMatrix()
{
	glPushMatrix();
}

void Renderer::popMatrix()
{
	glPopMatrix();
}

void Renderer::loadMatrix(const Matrix44f& m)
{
	glLoadMatrixf(&m[0]);
}

void Renderer::multMatrix(const Matrix44f& m)
{
	glMultMatrixf(&m[0]);
}

void Renderer::setRenderState(const RenderState& renderState, const uint32 value)
{
	static int glBlendSrcFunc = GL_ONE;
	static int glBlendDstFunc = GL_ZERO;

	switch (renderState)
	{
	case RS_CULLING			: 
		if (value) glEnable(GL_CULL_FACE);
		else glDisable(GL_CULL_FACE);
		break ;
	case RS_LIGHTING			:
		if (value) glEnable(GL_LIGHTING);
		else glDisable(GL_LIGHTING);
		break ;
	case RS_TEXTURE_1D		:
		if (value) glEnable(GL_TEXTURE_1D);
		else glDisable(GL_TEXTURE_1D);
		break ;
	case RS_TEXTURE_2D		:
		if (value) glEnable(GL_TEXTURE_2D);
		else glDisable(GL_TEXTURE_2D);
		break ;
	case RS_TEXTURE_3D		:
		if (value) glEnable(GL_TEXTURE_3D);
		else glDisable(GL_TEXTURE_3D);
		break ;
	case RS_BLENDING			:
		if (value) glEnable(GL_BLEND);
		else glDisable(GL_BLEND);
		break ;
	case RS_CULL_MODE		:
		glCullFace(_glCullMode[value]);
		break ;
	case RS_FILL_MODE		:
		glPolygonMode(GL_FRONT_AND_BACK, _glFillMode[value]);
		break ;
	case RS_SHADE_MODE		:
		glShadeModel(_glShadeMode[value]);
		break ;
	case RS_DEPTH_TEST		:
		if (value) glEnable(GL_DEPTH_TEST);
		else glDisable(GL_DEPTH_TEST);
		break ;
	case RS_DEPTH_FUNC		:
		glDepthFunc(_glCmpFunc[value]);
		break ;
	case RS_ALPHA_TEST :
		if (value) glEnable(GL_ALPHA_TEST);
		else glDisable(GL_ALPHA_TEST);
		break ;
	case RS_ALPHA_FUNC :
		glAlphaFunc(_glCmpFunc[value], 0.0f);
		break ;
	case RS_STENCIL_FUNC	:
		break ;
	case RS_BLEND_SRC_FUNC		:
		glBlendSrcFunc = _glBlendFunc[value];
		glBlendFunc(glBlendSrcFunc, glBlendDstFunc);
		break ;
	case RS_BLEND_DST_FUNC		:
		glBlendDstFunc = _glBlendFunc[value];
		glBlendFunc(glBlendSrcFunc, glBlendDstFunc);
		break ;
	case RS_BLEND_OP:
		glBlendEquationEXT(_glBlendOp[value]);
		break ;
	case RS_BLEND_COLOR: 
		//glBlendColorEXT(color.r(), color.g(), color.b(), color.a()); 
		break ;
	case RS_POINT_SPRITE:
		if (value) glEnable(GL_POINT_SPRITE_ARB);
		else glDisable(GL_POINT_SPRITE_ARB);
	}
	_currentState->value(renderState) = value;
}

void Renderer::toggle(const RenderState& renderState)
{
	setRenderState(renderState, !_currentState->value(renderState));
}

ITexture* Renderer::createTexture()
{
	return new Texture();
}

IShader* Renderer::createShader(const IShader::Type& type)
{
	return new Shader(type);
}

dw::graphics::Program* Renderer::createProgram()
{
	return new Program();
}

void Renderer::setLight(const int i, Light* light)
{
	assert(i < _caps.maxActiveLights);

	GLenum glLight = GL_LIGHT0 + i;

	if (light == NULL)
	{
		glDisable(glLight);
		return ;
	}
	glLightfv(glLight, GL_AMBIENT, &(light->getAmbient().r));	
	glLightfv(glLight, GL_DIFFUSE, &(light->getDiffuse().r));

	if (light->getType() == Light::POINT)
		glLightfv(glLight, GL_POSITION, &(light->getPosition().x));
	else
		glLightfv(glLight, GL_POSITION, &(light->getDirection().x));

	glEnable(glLight);
}

void Renderer::setMaterial(Material* material)
{
	if (!material)
	{
		setTexture(0, 0);
		//setTexture(TextureManager::instance().texture("wip.dds"));
		//glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		//glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		//glEnable(GL_TEXTURE_GEN_S); 
		//glEnable(GL_TEXTURE_GEN_T);
		//setRenderState(IRenderer::RS_DEPTH_TEST, false);
		//glDisable(GL_LIGHTING);
		//glEnable(GL_BLEND);
		//glBlendEquationEXT(GL_FUNC_ADD_EXT);
		//glBlendColorEXT(1, 0, 0, 0.5);
		//glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE
		//glBlendFunc(GL_ONE_MINUS_CONSTANT_COLOR_EXT, GL_CONSTANT_COLOR_EXT);
		return ;
	}
	if (material == _currentMaterial)
		return ;

	//setRenderState(IRenderer::RS_DEPTH_TEST, true);
	//glDisable(GL_BLEND);
	//glEnable(GL_LIGHTING);
	//glDisable(GL_TEXTURE_GEN_S); 
	//glDisable(GL_TEXTURE_GEN_T);

	_materialSwitchs++;

	Colorf specular = material->ks() * material->getSpecular();

	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, &material->getAmbient().r);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, &material->getDiffuse().r);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, &specular.r);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, &material->getEmissive().r);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material->getShininess() * 100);

	Pass* pass = material->getPass(0);
	setTexture(0, TextureManager::getInstance().loadTexture(pass->getTextureName(0)));
	//int uniformLocation = glGetUniformLocationARB(static_cast< Program* >(_currentProgram)->getGLHandle(), "diffuse");
	//glUniform1iARB(uniformLocation, 0);
	setTextureUnitState(0, Renderer::TS_TEXCOORD_INDEX, 0);
	setTextureUnitState(0, Renderer::TS_COLOR_OP,   Renderer::TextureOp::MODULATE);
	setTextureUnitState(0, Renderer::TS_COLOR_ARG0, Renderer::TextureArg::PREVIOUS);
	setTextureUnitState(0, Renderer::TS_COLOR_ARG1, Renderer::TextureArg::TEXTURE);
	// setTexture(1, TextureManager::getInstance().loadTexture(pass->getTextureName(1)));
	//uniformLocation = glGetUniformLocationARB(static_cast< Program* >(_currentProgram)->getGLHandle(), "ambient");
	//glUniform1iARB(glGetUniformLocationARB(static_cast< Program* >(_currentProgram)->getGLHandle(), "ambient"), 1);
	setTextureUnitState(1, Renderer::TS_TEXCOORD_INDEX, 2);
	setTextureUnitState(1, Renderer::TS_COLOR_OP,   Renderer::TextureOp::MODULATE);
	setTextureUnitState(1, Renderer::TS_COLOR_ARG0, Renderer::TextureArg::PREVIOUS);
	setTextureUnitState(1, Renderer::TS_COLOR_ARG1, Renderer::TextureArg::TEXTURE);

	_currentMaterial = material;
}

void Renderer::setTexture(ITexture* texture)
{
	if (!texture)
	{
		glBindTexture(GL_TEXTURE_2D, 0);
		glDisable(GL_TEXTURE_2D);
		return ;
	}

	glBindTexture(GL_TEXTURE_2D, static_cast< opengl::Texture* >(texture)->glId());
	glEnable(GL_TEXTURE_2D);
}

void Renderer::setTextureUnit(const int i)
{
	assert (i < _caps.maxTextureUnits);

	if (i != _currentTextureUnit)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB + i);
		_currentTextureUnit = i;
	}
}

void Renderer::setTexture(const int i, ITexture* texture)
{
	int tmp = _currentTextureUnit;
	setTextureUnit(i);
	setTexture(texture);
	setTextureUnit(tmp);
}

void Renderer::setTextureUnitState(const int i, const TextureUnitState& textureUnitState, const uint32 value)
{
	assert (i < _caps.maxTextureUnits);

	int tmp = _currentTextureUnit;

	// Set the texture unit to change state.
	setTextureUnit(i);

	switch (textureUnitState)
	{
	case TS_COLOR_OP		:
	case TS_ALPHA_OP		:
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
		glTexEnvi(GL_TEXTURE_ENV, glTexEnv(textureUnitState), glTexOp(value));
		break ;
	case TS_COLOR_ARG0	:
		glTexEnvi(GL_TEXTURE_ENV, glTexEnv(textureUnitState), glTexArg(value));
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
		break ;
	case TS_COLOR_ARG1	:	
		glTexEnvi(GL_TEXTURE_ENV, glTexEnv(textureUnitState), glTexArg(value));
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
		break ;
	case TS_COLOR_ARG2	:
		glTexEnvi(GL_TEXTURE_ENV, glTexEnv(textureUnitState), glTexArg(value));
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
		break ;
	case TS_ALPHA_ARG0	:
		glTexEnvi(GL_TEXTURE_ENV, glTexEnv(textureUnitState), glTexArg(value));
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_COLOR);
		break ;
	case TS_ALPHA_ARG1	:
		//glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
		glTexEnvi(GL_TEXTURE_ENV, glTexEnv(textureUnitState), glTexArg(value));
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
		break ;
	case TS_ALPHA_ARG2	:
		//glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
		glTexEnvi(GL_TEXTURE_ENV, glTexEnv(textureUnitState), glTexArg(value));
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, GL_SRC_ALPHA);
		break ;
	case TS_FILTER_MODE :
		switch (value)
		{
		case TextureFilterMode::POINT :
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			break ;
		case TextureFilterMode::LINEAR :
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			break ;
		case TextureFilterMode::BILINEAR :
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
			break ;
		case TextureFilterMode::TRILINEAR :
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
			break ;
		}
		break ;
	case TS_ADDRESS_U	:
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glTexAddressMode(value));
		break ;
	case TS_ADDRESS_V	:
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glTexAddressMode(value));
		break ;
	case TS_TEXCOORD_INDEX :
		break ;
	case TS_POINT_SPRITE :
		glTexEnvf(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, (GLfloat)value);
		break ;
	}

	// Track the state change.
	_textureUnits[i].state.value(textureUnitState) = value;

	// Set the current texture unit back to its previous value.
	setTextureUnit(tmp);
}

void Renderer::setVertexBuffer(VertexBuffer* buffer)
{
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
	glDisableVertexAttribArrayARB(3);
	glDisableVertexAttribArrayARB(4);
	glDisableVertexAttribArrayARB(5);
	glDisableVertexAttribArrayARB(6);

	for (int i = 0; i < _caps.maxTextureUnits; ++i)
	{	
		glClientActiveTextureARB(GL_TEXTURE0_ARB + i);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
	glClientActiveTextureARB(GL_TEXTURE0_ARB);

	glColor4f(1,1,1,1);

	if (buffer == NULL)
		return ;

	buffer->bind();

	const VertexFormat* format = buffer->getVertexFormat();
	for (dw::uint32 i = 0; i < format->getComponentsCount(); ++i)
	{
		const VertexFormat::Component& component = format->getComponent(i);

		void* bufferData = reinterpret_cast< void* >(component.offset);

		dw::uint32 itemsCount = VertexElement::itemsCount(component.decl.type);
		GLenum glItemsType = VertexElement::glItemsType(component.decl.type);

		switch (component.decl.semantic)
		{
		case VertexElement::position :
			glEnableClientState(GL_VERTEX_ARRAY);
			glVertexPointer(itemsCount, glItemsType, format->getVertexSize(), bufferData);
			break ;
		case VertexElement::normal :
			glEnableClientState(GL_NORMAL_ARRAY);
			glNormalPointer(glItemsType, format->getVertexSize(), bufferData);
			break ;
		case VertexElement::texcoord :
			for (int j = 0; j < _caps.maxTextureUnits; ++j)
			{
				if (_textureUnits[j].state.value(TS_TEXCOORD_INDEX) == component.decl.semanticIndex)
				{
					glClientActiveTextureARB(GL_TEXTURE0_ARB + j);
					glEnableClientState(GL_TEXTURE_COORD_ARRAY);
					glTexCoordPointer(itemsCount, glItemsType, format->getVertexSize(), bufferData);
				}
			}
			break ;
		case VertexElement::color :
			glEnableClientState(GL_COLOR_ARRAY);
			glColorPointer(itemsCount, glItemsType, format->getVertexSize(), bufferData);
			break ;
		/*
		case VertexElement::BINORMAL	:
			glEnableVertexAttribArrayARB(3);
			glVertexAttribPointerARB(3, component.getCount(), glDataType(component.getDataType()), GL_FALSE, format.getSize(), bufferData);
			break ;
		case VertexElement::TANGENT		:
			glEnableVertexAttribArrayARB(4);
			glVertexAttribPointerARB(4, component.getCount(), glDataType(component.getDataType()), GL_FALSE, format.getSize(), bufferData);
			break ;
		case VertexElement::BLEND_INDICES	:
			glEnableVertexAttribArrayARB(5);
			glVertexAttribPointerARB(5, component.getCount(), glDataType(component.getDataType()), GL_FALSE, format.getSize(), bufferData);
			break ;
		case VertexElement::BLEND_WEIGHTS	:
			glEnableVertexAttribArrayARB(6);
			glVertexAttribPointerARB(6, component.getCount(), glDataType(component.getDataType()), GL_FALSE, format.getSize(), bufferData);
			break ;
		*/
		}
	}
}

void Renderer::setIndexBuffer(IndexBuffer* buffer)
{
	if (m_indexBuffer != buffer)
	{
		m_indexBuffer = buffer;

		if (m_indexBuffer != NULL)
		{
			m_indexBuffer->bind();
		}
	}
}

void Renderer::setProgram(dw::graphics::Program* program)
{
	if (!program)
	{
		glUseProgramObjectARB(0);
		_currentProgram = 0;
	}
	else if (_currentProgram != program)
	{
		glUseProgramObjectARB(static_cast< Program* >(program)->getGLId());
		_currentProgram = program;
	}
}

void Renderer::drawRect(const unsigned x, const unsigned y, const unsigned width, const unsigned height, const bool filled) const
{
	if (filled)
		glBegin(GL_QUADS);
	else
		glBegin(GL_LINE_LOOP);
	glTexCoord2f(0, 0); glVertex2i(x, y);
	glTexCoord2f(1, 0); glVertex2i(x + width, y); 
	glTexCoord2f(1, 1); glVertex2i(x + width, y + height);
	glTexCoord2f(0, 1); glVertex2i(x, y + height);
	glEnd();
}

void Renderer::drawCircle(const unsigned radius)
{
	glBegin(GL_LINE_LOOP);
	for (int i=0; i < 360; i++)
	{
			//float degInRad = i * DEG2RAD;
			//glVertex2f(cos(degInRad) * radius, sin(degInRad) * radius);
	}
	glEnd();
}

void Renderer::drawBox(const float& width, const float& height, const float& depth)
{
	glPushMatrix();
	glScalef(width, height, depth);
	glutWireCube(1.f);
	glPopMatrix();
}

void Renderer::drawBox(const Cuboid& box)
{
	glPushMatrix();
	glTranslatef(box.center.x, box.center.y, box.center.z);
	drawBox(box.width, box.height, box.depth);
	glPopMatrix();
}

void Renderer::drawSphere(const float& radius)
{
	glPushMatrix();
	glutSolidSphere(radius, 10, 10);
	glPopMatrix();
}

void Renderer::drawTeapot(const float& size)
{
	glPushMatrix();
	glutSolidTeapot(size);
	glPopMatrix();
}

void Renderer::drawText(Font* font, const std::string& text)
{
	GLint   viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0, (float)1024, (float)768, 0, -1.f, 1.f);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();

	glPushAttrib(GL_ALL_ATTRIB_BITS);

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glDisable(GL_LIGHTING);
	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	float x = 0, y = (float)font->_height;
	for (unsigned i = 0; i < text.size(); ++i)
	{
		if (text[i] == '\n')
		{
			x = 0;
			y += (float)font->_height / 0.75f;
		}
		else
		{
			Font::Glyph glyph = font->getGlyph(text[i]);
	
			glBindTexture(GL_TEXTURE_2D, glyph.texID);

			float u = (float)glyph.width	/ (float)font->_width;
			float v = (float)glyph.height	/ (float)font->_height;

			glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex2f(x + glyph.xoffset, y - glyph.yoffset);
			glTexCoord2f(0, v); glVertex2f(x + glyph.xoffset, y + glyph.height - glyph.yoffset);
			glTexCoord2f(u, v); glVertex2f(x + glyph.xoffset + glyph.width, y + glyph.height - glyph.yoffset);
			glTexCoord2f(u, 0); glVertex2f(x + glyph.xoffset + glyph.width, y - glyph.yoffset);
			glEnd();
			x += glyph.xadvance >> 6;
		}
	}

	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);

	glPopAttrib();
	
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void Renderer::drawGrid(const int size, const int unit)
{
	int halfSize = size / 2;

	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);
	glColor3f(0.5f, 0.5f, 0.5f);
	for (int i = -halfSize; i <= halfSize; i += unit)
	{
		if (i == 0)	glLineWidth(4.0f);
		else if (((i / unit) % 5) == 0) glLineWidth(2.0f);
		else glLineWidth(1.0f);
		glBegin(GL_LINES);
		glVertex3f(-halfSize, 0, i);
		glVertex3f(halfSize, 0, i);

		glVertex3f(i, 0, -halfSize);
		glVertex3f(i, 0, halfSize);
		glEnd();
	}
	glPopAttrib();
}

GLenum Renderer::glDataType(const uint32 dataType)
{
	switch (dataType)
	{
	case DataType::BYTE						: return GL_BYTE;						break ;
	case DataType::UNSIGNED_BYTE	:	return GL_UNSIGNED_INT;		break ;
	case DataType::SHORT					:	return GL_SHORT;					break ;
	case DataType::UNSIGNED_SHORT	:	return GL_UNSIGNED_SHORT; break ;
	case DataType::INT						:	return GL_INT;						break ;
	case DataType::UNSIGNED_INT		:	return GL_UNSIGNED_INT;		break ;
	case DataType::FLOAT					:	return GL_FLOAT;					break ;
	case DataType::DOUBLE					:	return GL_DOUBLE;					break ;
	default												:	return GL_ZERO;
	}
}

GLenum Renderer::glPrimitiveType(const uint32 primitiveType)
{
	switch (primitiveType)
	{
	case PrimitiveType::POINTS					: return GL_POINTS;					break ;
	case PrimitiveType::LINES						:	return GL_LINES;					break ;
	case PrimitiveType::TRIANGLES				:	return GL_TRIANGLES;			break ;
	case PrimitiveType::TRIANGLE_STRIP	:	return GL_TRIANGLE_STRIP; break ;
	case PrimitiveType::QUADS						:	return GL_QUADS;					break ;
	default															:	return GL_ZERO;
	}
}

GLenum Renderer::glTexEnv(const uint32 tus)
{
	switch (tus)
	{
	case TS_COLOR_OP		:	return GL_COMBINE_RGB_ARB;		break ;
	case TS_COLOR_ARG0	:	return GL_SOURCE0_RGB_ARB;		break ;
	case TS_COLOR_ARG1	:	return GL_SOURCE1_RGB_ARB;		break ;
	case TS_COLOR_ARG2	:	return GL_SOURCE2_RGB_ARB;		break ;
	case TS_ALPHA_OP		:	return GL_COMBINE_ALPHA_ARB;	break ;
	case TS_ALPHA_ARG0	:	return GL_SOURCE0_ALPHA_ARB;	break ;
	case TS_ALPHA_ARG1	:	return GL_SOURCE1_ALPHA_ARB;	break ;
	case TS_ALPHA_ARG2	:	return GL_SOURCE2_ALPHA_ARB;	break ;
	default							:	return GL_ZERO;
	}
}

GLenum Renderer::glTexOp(const uint32 top)
{
	switch (top)
	{
	case TextureOp::REPLACE			:	return GL_REPLACE;			break ;
	case TextureOp::MODULATE		:	return GL_MODULATE;			break ;
	case TextureOp::ADD					:	return GL_ADD;					break ;
	case TextureOp::SUBTRACT		:	return GL_SUBTRACT;			break ;
	case TextureOp::INTERPOLATE	: return GL_INTERPOLATE;	break ;
	default											: return GL_ZERO;
	}
}

GLenum Renderer::glTexArg(const uint32 texArg)
{
	switch (texArg)
	{
	case TextureArg::TEXTURE	:	return GL_TEXTURE;						break ;
	case TextureArg::CONSTANT	:	return GL_CONSTANT_ARB;				break ;
	case TextureArg::DIFFUSE	:	return GL_PRIMARY_COLOR_ARB;	break ;
	case TextureArg::PREVIOUS	:	return GL_PREVIOUS_ARB;				break ;
	default										: return GL_ZERO;
	}
}

GLenum Renderer::glTexAddressMode(const uint32 texAddressMode)
{
	switch (texAddressMode)
	{
	case TextureAdressMode::CLAMP	: return GL_CLAMP;	break ;
	case TextureAdressMode::REPEAT : return GL_REPEAT; break ;
	default												: return GL_ZERO;
	}
}

std::string Renderer::glErrorString(const GLenum glError)
{
	switch (glError)
	{
	case GL_NO_ERROR					: return "No error has been recorded.";																			break ;
	case GL_INVALID_ENUM			: return "An unacceptable value is specified for an enumerated argument.";	break ;
	case GL_INVALID_VALUE			: return "A numeric argument is out of range.";															break ;
	case GL_INVALID_OPERATION	: return "The specified operation is not allowed in the current state.";		break ;
	case GL_STACK_OVERFLOW		: return "This command would cause a stack overflow.";											break ;
	case GL_STACK_UNDERFLOW		: return "This command would cause a stack underflow.";											break ;
	case GL_OUT_OF_MEMORY			: return "There is not enough memory left to execute the command.";					break ;
	default										: return "Unknown error.";
	}
}

} // namespace opengl
} // namespace graphics
} // namespace dw
